/*
Copyright (c) 2010, Luca Deltodesco
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this list of conditions
	  and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, this list of
	  conditions and the following disclaimer in the documentation and/or other materials provided
	  with the distribution.
    * Neither the name of the caxe project nor the names of its contributors may be used to endorse
	 or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package ;

using FUtil;
using SUtil;
using SUse;

enum DatType {
	dString;
	dScope;
	dMacro;
}

class Datum {
	public var type:DatType;
	
	public var str:String;
	public var macro:Macro;
	public var scope:Scope;
	
	public function new(t:DatType) {
		type = t;
		str = "";
		macro = null;
		scope = null;
	}
}

class Scope {
	public var parent:Scope;
	public var file:Scope;
	public var actfile:File;
	public var object:Scope;
	public var method:Scope;
	
	public var locals:Array<Macro>;
	public var data:Array<Datum>;
	
	public function new() {
		parent = file = object = method = null;
		actfile = null;
		
		locals = new Array<Macro>();
		data = new Array<Datum>();
	}
	
	inline function insert(ret:Array<Macro>, val:Macro) {
		var found = false;
		for (i in ret) {
			if (i == val) {
				found = true;
				break;
			}
		}
		if (!found)
			ret.push(val);
	}
	
	inline function addall(ret:Array<Macro>, list:Array<Macro>) {
		for (i in list) insert(ret, i);
	}
	
	public function query() {
		var ret = new Array<Macro>();
		
		addall(ret, locals);
		var cur = parent;
		while (cur != null) {
			addall(ret, cur.locals);
			cur = cur.parent;
		}
		addall(ret, actfile.locals);
		
		for (i in actfile.imports) {
			addall(ret, i.locals);
		}

		return ret;
	}
	
	public function build() {
		var ret = "";
		for (i in data) {
			switch(i.type) {
				case dString: ret += i.str;
				case dScope:  ret += "{" + i.scope.build() + "}";
				case dMacro:  ret += i.macro.update();
			}
		}
		return ret;
	}
}

class File {
	public var name:String;
	public var path:String;
	public var tpath:String;
	
	public var imports:Array<File>;
	public var locals:Array<Macro>;
	
	public var extras:Array<String>;
	
	public var packimpo:Array<String>;
	public var packname:String;
	
	public var importmacs:Array<String>;
	public var data:Scope;
	
	public function build() {
		var ret = "";
		ret += "package " + packname + ";\n";
		for (i in packimpo) ret += "import " + i + ";\n";
		if (extras.length > 0) ret += "\n//'newfile' generated imports\n";
		for (i in extras) ret += i;
		ret += "\n";
		return ret + data.build();
	}
	
	public function new() {
		name = path = tpath = packname = "";
		imports = new Array<File>();
		extras = new Array<String>();
		packimpo = new Array<String>();
		importmacs = new Array<String>();
		locals = new Array<Macro>();
	}
}

class Post {
	public var isString:Bool;
	
	public var str:String;
	public var argn:Int;
	
	public function new(str:Bool) {
		isString = str;
		argn = 0;
		this.str = "";
	}
}

class Instance {
	public var mname:String;
	public var compilation:Scope;
	
	public function new() {
		mname = "";
		compilation = null;
	}
}

enum MacroType {
	mDefine;
	mExpand;
	mMixin;
}
class Macro {
    public var newfile:Bool;

	public var name:String;
	public var type:MacroType;
	public var argc:Int;
	public var postamble:Array<Post>;
	
	public var preamble:String;
	public var instances:Array<Instance>;
	public var scope:Scope;
	
	public function new(t:MacroType) {
		type = t;
		name = "";
		argc = 0;
		postamble = new Array<Post>();
		preamble = "";
		instances = new Array<Instance>();
		scope = null;
	}
	
	public function compile(mname:String, args:Array<String>) {
		var ret = preamble;
		ret += switch(type) {
			case mMixin: "";
			case mExpand: "";
			default: mname;
		}
		for (c in postamble) {
			if (c.isString) ret += c.str;
			else            ret += args[c.argn];
		}
		return ret;
	}
	public function update() {
		if (type == mMixin) return "";
		else {
			var ret = "";
			for (i in instances) {
				ret += i.compilation.build();
			}
			return ret;
		}
	}
	
	public function equal(t:Macro) {
		return name == t.name && argc == t.argc;
	}
}
